﻿namespace NTLib.Pipe.Network.UTest.Connectors
{
    using Descriptors;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Network.Connectors;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using NTLib.Core.Win.Net;
    using NTLib.Core.Tasks;
    using Core.ScopeLock;
    using System.IO;
    using Containers;
    using Formats;
    using Streaming.Descriptors.Packages;
    using Streaming.Packages;
    using Streaming.Formats;

    /// <summary>
    /// Test the connector TCP 
    /// </summary>
    [TestClass]
    public class TcpConnectorUTest
    {
        #region Neasted

        public class Test
        {
            public string Msg { get; set; }
        }

        #endregion

        #region Methods

        [TestCategory("Pipe.Network.Connector")]
        [TestMethod]
        public void TcpConnetor_Initialize()
        {
            /// TO CHECK
            using (TcpConnector connector = new TcpConnector())
            {
                Assert.IsFalse(connector.IsInitialized);
                Assert.IsFalse(connector.IsDisposed);

                connector.InitializeAsync().Wait();

                Assert.IsTrue(connector.IsInitialized);
                Assert.IsFalse(connector.IsDisposed);
            }
        }

        [TestCategory("Pipe.Network.Connector")]
        [TestMethod]
        public void TcpConnector_ConnectionSimple()
        {
            CancellationTokenSource testTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(150));

            var ipaddr = IPAddress.Loopback;
            var port = ipaddr.GetFreePort();
            Assert.AreNotEqual(port, -1, "Not free port found");

            TcpListener listener = new TcpListener(ipaddr, port);

            using (ScopeLockItem<TcpListener> sclpListner = new ScopeLockItem<TcpListener>(listener, (t) => t.Stop()))
            {
                listener.Start();

                TcpClient accepted = null;

                try
                {
                    var acceptTask = listener.AcceptTcpClientAsync().ContinueWith(t =>
                    {
                        Assert.IsNotNull(t.Result);
                        Assert.IsTrue(t.Result.Connected);
                        accepted = t.Result;
                        Console.WriteLine("Connection establish");
                    });

                    NetworkAddressDescription addr = new NetworkAddressDescription(new Uri("tcp://" + ipaddr.ToString() + ":" + port, UriKind.RelativeOrAbsolute));

                    using (TcpConnector connector = new TcpConnector())
                    {
                        connector.InitializeAsync().Wait();
                        Assert.IsFalse(connector.Connected == ConnectionStatus.Connected);
                        connector.ConnectAsync(addr).Wait(testTimeout.Token);
                        Assert.IsTrue(connector.Connected == ConnectionStatus.Connected);

                        acceptTask.Wait(testTimeout.Token);

                        connector.CloseAsync().Wait(testTimeout.Token);

                        Assert.IsNotNull(accepted);
                        Assert.IsFalse(connector.Connected == ConnectionStatus.Connected);

                        try
                        {
                            accepted.GetStream();
                        }
                        catch (InvalidOperationException)
                        {
                            Assert.IsFalse(accepted.Connected);
                        }
                        catch (Exception ex)
                        {
                            Assert.Fail(ex.Message);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Assert.Fail(ex.Message);
                }
            }
        }

        [TestCategory("Pipe.Network.Connector")]
        [TestMethod]
        public void TcpConnector_ReceivedData()
        {
            CancellationTokenSource testTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));

            var ipaddr = IPAddress.Loopback;
            var port = ipaddr.GetFreePort();
            Assert.AreNotEqual(port, -1, "Not free port found");

            TcpListener listener = new TcpListener(ipaddr, port);

            using (ScopeLockItem<TcpListener> sclpListner = new ScopeLockItem<TcpListener>(listener, (t) => t.Stop()))
            {
                listener.Start();

                TcpClient accepted = null;

                try
                {
                    var acceptTask = listener.AcceptTcpClientAsync().ContinueWith(t =>
                    {
                        Assert.IsNotNull(t.Result);
                        Assert.IsTrue(t.Result.Connected);
                        accepted = t.Result;
                        Console.WriteLine("Connection establish");
                    });

                    NetworkAddressDescription addr = new NetworkAddressDescription(new Uri("tcp://" + ipaddr.ToString() + ":" + port, UriKind.RelativeOrAbsolute));

                    using (TcpConnector connector = new TcpConnector())
                    {
                        connector.InitializeAsync().Wait();
                        Assert.IsFalse(connector.Connected == ConnectionStatus.Connected);
                        connector.ConnectAsync(addr).Wait(testTimeout.Token);
                        Assert.IsTrue(connector.Connected == ConnectionStatus.Connected);

                        acceptTask.Wait(testTimeout.Token);

                        SimpleHeaderPackage pkg = new SimpleHeaderPackage();

                        var readerTask = connector.ReadAsync(pkg, testTimeout.Token);

                        string msgStr = "poney";
                        var msg = Encoding.ASCII.GetBytes(msgStr);
                        var msgByte = BitConverter.GetBytes(msg.Length);

                        using (Stream stream = accepted.GetStream())
                        {
                            List<byte> fullMsg = new List<byte>(msgByte);
                            fullMsg.AddRange(msg);

                            stream.Write(fullMsg.ToArray(), 0, fullMsg.Count);

                            readerTask.Wait(testTimeout.Token);

                            Assert.IsNotNull(readerTask.Result);
                            Assert.IsNotNull(readerTask.Result.Stream);
                            Assert.IsTrue(readerTask.Result.Stream.Length > 0);
                            Assert.IsTrue(readerTask.Result.Stream.Position == 0);
                            using (var result = readerTask.Result)
                            {
                                using (StreamReader reader = new StreamReader(result.Stream))
                                {
                                    var readMsg = reader.ReadToEnd();
                                    Assert.AreEqual(readMsg, msgStr);
                                }
                            }
                        }

                        Assert.IsTrue(readerTask.IsCompleted);

                        connector.CloseAsync().Wait(testTimeout.Token);

                        Assert.IsNotNull(accepted);
                        Assert.IsFalse(connector.Connected == ConnectionStatus.Connected);

                        try
                        {
                            accepted.GetStream();
                        }
                        catch (InvalidOperationException)
                        {
                            Assert.IsFalse(accepted.Connected);
                        }
                        catch (Exception ex)
                        {
                            Assert.Fail(ex.Message);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Assert.Fail(ex.Message);
                }
            }
        }

        [TestCategory("Pipe.Network.Connector")]
        [TestMethod]
        public void TcpConnector_SendData()
        {
            CancellationTokenSource testTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));

            var ipaddr = IPAddress.Loopback;
            var port = ipaddr.GetFreePort();
            Assert.AreNotEqual(port, -1, "Not free port found");

            TcpListener listener = new TcpListener(ipaddr, port);

            using (ScopeLockItem<TcpListener> sclpListner = new ScopeLockItem<TcpListener>(listener, (t) => t.Stop()))
            {
                listener.Start();

                TcpClient accepted = null;

                try
                {
                    var acceptTask = listener.AcceptTcpClientAsync().ContinueWith(t =>
                    {
                        Assert.IsNotNull(t.Result);
                        Assert.IsTrue(t.Result.Connected);
                        accepted = t.Result;
                        Console.WriteLine("Connection establish");
                    });

                    NetworkAddressDescription addr = new NetworkAddressDescription(new Uri("tcp://" + ipaddr.ToString() + ":" + port, UriKind.RelativeOrAbsolute));

                    using (TcpConnector connector = new TcpConnector())
                    {
                        connector.InitializeAsync().Wait();
                        Assert.IsFalse(connector.Connected == ConnectionStatus.Connected);
                        connector.ConnectAsync(addr).Wait(testTimeout.Token);
                        Assert.IsTrue(connector.Connected == ConnectionStatus.Connected);

                        acceptTask.Wait(testTimeout.Token);

                        string msgStr = "poney";
                        var msg = Encoding.ASCII.GetBytes(msgStr);
                        var msgByte = BitConverter.GetBytes(msg.Length);

                        List<byte> fullMsg = new List<byte>(msgByte);
                        fullMsg.AddRange(msg);

                        IPackageDescriptor reader = new SimpleHeaderPackage();
                        SimplePackage readPkg = new SimplePackage();
                        SimplePackage pkg = new SimplePackage();
                        pkg.Stream.Write(fullMsg.ToArray(), 0, fullMsg.Count);
                        pkg.Stream.Seek(0, SeekOrigin.Begin);

                        using (Stream stream = accepted.GetStream())
                        {
                            var sendTask = connector.SendAsync(pkg, testTimeout.Token);

                            sendTask.Wait(testTimeout.Token);

                            do
                            {
                                byte[] bytes = new byte[reader.Size];
                                stream.ReadAsync(bytes, 0, reader.Size, testTimeout.Token).Wait();

                                reader.AddDataToStream(bytes, readPkg.Stream);

                                reader = reader.HowToReadNext(bytes);

                                testTimeout.Token.ThrowIfCancellationRequested();

                            }
                            while (reader != null);

                            readPkg.Stream.Seek(0, SeekOrigin.Begin);

                            using (StreamReader r = new StreamReader(readPkg.Stream))
                            {
                                string msgReceived = r.ReadToEnd();
                                Assert.AreEqual(msgReceived, msgStr);
                            }

                            Assert.IsTrue(sendTask.IsCompleted);
                        }

                        connector.CloseAsync().Wait(testTimeout.Token);

                        Assert.IsNotNull(accepted);
                        Assert.IsFalse(connector.Connected == ConnectionStatus.Connected);

                        try
                        {
                            accepted.GetStream();
                        }
                        catch (InvalidOperationException)
                        {
                            Assert.IsFalse(accepted.Connected);
                        }
                        catch (Exception ex)
                        {
                            Assert.Fail(ex.Message);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Assert.Fail(ex.Message);
                }
            }
        }

        [TestCategory("Pipe.Network.Connector")]
        [TestMethod]
        public void TcpConnector_AcceptAndCo()
        {
            CancellationTokenSource testTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));

            var ipaddr = IPAddress.Loopback;
            var port = ipaddr.GetFreePort();
            Assert.AreNotEqual(port, -1, "Not free port found");

            NetworkAddressDescription desc = new NetworkAddressDescription(new Uri("TCP://" + ipaddr.ToString() + ":" + port, UriKind.RelativeOrAbsolute));

            using (TcpConnector acceptor = new TcpConnector())
            {
                acceptor.InitializeAsync(true).Wait(testTimeout.Token);

                using (TcpConnector connector = new TcpConnector())
                {
                    connector.InitializeAsync().Wait(testTimeout.Token);

                    var acceptorCoTask = acceptor.ConnectAsync(desc, testTimeout.Token);

                    while (acceptor.Connected != ConnectionStatus.Waiting)
                        Task.Delay(10).Wait(testTimeout.Token);

                    connector.ConnectAsync(desc, testTimeout.Token).Wait(testTimeout.Token);
                    
                    acceptorCoTask.Wait(testTimeout.Token);

                    Assert.IsTrue(acceptor.Connected == ConnectionStatus.Connected);
                    Assert.IsTrue(connector.Connected == ConnectionStatus.Connected);

                    acceptor.CloseAsync(testTimeout.Token).Wait(testTimeout.Token);

                    Assert.IsFalse(acceptor.Connected == ConnectionStatus.Connected);
                }
            }
        }

        [TestCategory("Pipe.Network.Connector")]
        [TestMethod]
        public void TcpConnector_AcceptAndCo_sendAndReceive()
        {
            JsonPackageFormater formatter = new SimpleJsonPackageFormater();

            CancellationTokenSource testTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));

            var ipaddr = IPAddress.Loopback;
            var port = ipaddr.GetFreePort();
            Assert.AreNotEqual(port, -1, "Not free port found");

            NetworkAddressDescription desc = new NetworkAddressDescription(new Uri("TCP://" + ipaddr.ToString() + ":" + port, UriKind.RelativeOrAbsolute));

            using (TcpConnector acceptor = new TcpConnector())
            {
                acceptor.InitializeAsync(true).Wait(testTimeout.Token);

                using (TcpConnector connector = new TcpConnector())
                {
                    connector.InitializeAsync().Wait(testTimeout.Token);

                    var acceptorCoTask = acceptor.ConnectAsync(desc, testTimeout.Token);

                    while (acceptor.Connected != ConnectionStatus.Waiting)
                        Task.Delay(10).Wait(testTimeout.Token);

                    connector.ConnectAsync(desc, testTimeout.Token).Wait(testTimeout.Token);

                    acceptorCoTask.Wait(testTimeout.Token);

                    Assert.IsTrue(acceptor.Connected == ConnectionStatus.Connected);
                    Assert.IsTrue(connector.Connected == ConnectionStatus.Connected);

                    Test tOrigin = new Test() { Msg = "poney" };

                    connector.SendAsync(formatter.ToPackage(tOrigin), testTimeout.Token).Wait();

                    var readObj = acceptor.ReadAsync(formatter.ReaderDescriptor, testTimeout.Token).WaitResult(testTimeout.Token);

                    Test result = formatter.FromPackage<Test>(readObj);

                    Assert.AreEqual(result.Msg, tOrigin.Msg);

                    acceptor.CloseAsync(testTimeout.Token).Wait(testTimeout.Token);

                    Assert.IsFalse(acceptor.Connected == ConnectionStatus.Connected);
                }
            }
        }

        [TestCategory("Pipe.Network.Connector")]
        [TestMethod]
        public void TcpConnector_CancelDataTransfert()
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
